#!/usr/bin/env python
# encoding: utf-8

import paho.mqtt.client as mqtt
import re
import json
import time
import threading
import binascii
import serial
import serial.tools.list_ports
from datetime import datetime
from Serial import Serial
from Serial import Ser

# 服务器配置
SERVER_ID = 'S001'
USER_ID = '17740171896'

# mqtt客户端配置
MQTT_BROKER_URL = 'localhost'
MQTT_BROKER_PORT = 1883
MQTT_CLIENT_ID = "53010000"
MQTT_USERNAME = ''
MQTT_PASSWORD = ''
MQTT_KEEPALIVE = 60
MQTT_TLS_ENABLED = False

# 串口配置
SERIAL_TIMEOUT= 0.5
SERIAL_PORT = "/dev/ttyUSB0"
SERIAL_BAUDRATE = 115200
SERIAL_BYTESIZE= 8
SERIAL_PARITY= 'N'
SERIAL_STOPBITS= 1

# 节点阀门命令
NODE_ONLINE       = "A1"
NODE_OFFLINE      = "A2"
NODE_OPEN_TAP     = "A3"
NODE_CLOSE_TAP    = "A4"
NODE_OPEN_TAP_OK  = "A5"
NODE_CLOSE_TAP_OK = "A6"

NODE_REQUEST_CONNECT = "C0"
NODE_RESPONSE_CONNECT = "C1"
NODE_ALIVE = "C2"

SITE_ONLINE = "B1"
SITE_OFFLINE = "B2"

#  REG_TELEPHONE_SITE = re.compile(r'^user/([0-9]{11})/site/([a-z0-9]{8})')
#  REG_TELEPHONE_NODE = re.compile(r'^user/([0-9]{11})/node/([a-z0-9]{8})')
REG_DEVICE_NODE = re.compile(r'^dev/([a-z0-9]{8})')
REG_DEVICE_SITE = re.compile(r'^dev/([a-z0-9]{8})')

reg = {
    'nodes':re.compile(r'^dev/([a-z0-9]{8})/nodes')
}


def log_style(msg):
    print('raspberry'+'*'*50)
    print('time: ',datetime.today())
    print('print: ',msg)


#  ser = Serial()
#  ser.init_config( "/dev/ttyUSB0", 115200, 0.5)

# 所有节点信息
# nodes = {}
# class Node():
    # name      = ''
    # cid       = ''
    # lora_addr = ''
    # number    = ''
    # version   = ''
    # position  = ''
    # tap_status    = False


#  @ser.on_message()
#  def serial_message(msg):
    #  print("serial receive message：", msg)

        #  client.publish('user/17740171896/site/53010000',payload="")
        #  client.publish('user/17740171896/node/4e010001',payload="tap is open")
        #  client.publish('user/17740171896/node/4e010002',payload="tap is close")

#  client = mqtt.Client(client_id=MQTT_CLIENT_ID)
#  client.on_connect = on_connect
#  client.on_message = on_message
#  client.connect(MQTT_BROKER_URL, MQTT_BROKER_PORT, MQTT_KEEPALIVE)

#  th_do = threading.Thread(target=do_something)
#  th_do.setDaemon(True)
#  th_do.start()

class Device():
    def __init__(self):
        self.nodes = []

    def find_node(self, cid):
        for node in self.nodes:
            if node['cid'] == cid:
                return node

class Raspberry():
    """
        树莓派mqtt连接服务器，打开串口通信，控制设备
    """
    # 获取节点标志
    get_nodes_flag = False

    def __init__(self, ser=None, mqtt=None):
        self.device = Device()
        self.ser = None
        self.client = None
        # 开始运行
        self.all_run()

    def all_run(self):
        self.ser_run()
        th_do = threading.Thread(target=self.do_something)
        th_do.setDaemon(True)
        th_do.start()
        thread_heart = threading.Thread(target=self.heartHandler)
        thread_heart.setDaemon(True)
        thread_heart.start()
        self.mqtt_run()

    def ser_run(self):
        self.ser = Ser()
        self.ser.serial.port     = SERIAL_PORT
        self.ser.serial.baudrate = SERIAL_BAUDRATE
        self.ser.serial.timeout  = SERIAL_TIMEOUT
        self.ser.on_message = self.ser_on_message
        self.ser.on_log = self.ser_logging
        self.ser.loop_start()

    def mqtt_run(self):
        # 连接mqtt服务器
        self.client = mqtt.Client(client_id=MQTT_CLIENT_ID)
        self.client.on_connect = self.mqtt_on_connect
        self.client.on_message = self.mqtt_on_message
        self.client.connect(MQTT_BROKER_URL, MQTT_BROKER_PORT, MQTT_KEEPALIVE)
        self.client.loop_forever()

    def mqtt_on_connect(self,client, userdata, flags, rc):
        """ The callback for when the client receives a CONNACK response from the server.
        """
        log_style("Connected with result code "+str(rc))
        # 此处订阅所有设备的route
        self.client.subscribe("dev/%s/#"%MQTT_CLIENT_ID)
        self.client.subscribe("dev/4e010001/#")
        self.client.subscribe("dev/4e010002/#")

    def mqtt_on_message(self, client, userdata, msg):
        """ The callback for when a PUBLISH message is received from the server.
        """
        # log_style(msg.topic+" "+str(msg.payload))
        topic = msg.topic
        payload = msg.payload.decode('utf-8')
        if REG_DEVICE_NODE.match(topic):
            res = REG_DEVICE_NODE.match(topic)
            cid = res.group(1)
            if payload == NODE_OPEN_TAP:
                print('接收到命令: NODE_OPEN_TAP', NODE_OPEN_TAP)
                self.ser_send_to_node(cid=cid, cmd=payload)
            if payload == NODE_CLOSE_TAP:
                print('接收到命令: NODE_CLOSE_TAP', NODE_CLOSE_TAP)
                self.ser_send_to_node(cid=cid, cmd=payload)
        if reg['nodes'].match(topic):
            res = reg['nodes'].match(topic)
            cid = res.group(1)
            if cid == MQTT_CLIENT_ID:
                self.get_nodes_flag = True
                self.device.nodes = json.loads(payload)
                for node in self.device.nodes:
                    node['alive_time'] = datetime.now()
                    print("节点详情:")
                    for n in node.items():
                        print(n)
                # 关闭节点获取

    def mqtt2user(self, cid, msg):
        """mqtt发送消息到用户"""
        self.client.publish('user/%s/dev/%s'%(USER_ID,cid),payload=msg)

    def ser_on_message(self, msg):
        recv = str(binascii.b2a_hex(msg).decode('utf-8')).upper()
        print("串口接收消息：", recv)
        cid, cmd, content = self.cmd_deal(recv)
        if cid and cmd:
            print("接收串口到地址和命令")
            print(cid, cmd)
            if cmd == NODE_OPEN_TAP_OK:
                print("发送打开成功")
                self.client.publish('user/%s/dev/%s'%(USER_ID,cid),payload=NODE_OPEN_TAP_OK)
            if cmd == NODE_CLOSE_TAP_OK:
                print("发送关闭成功")
                self.client.publish('user/%s/dev/%s'%(USER_ID,cid),payload=NODE_CLOSE_TAP_OK)
            if cmd == NODE_ALIVE:
                print("%s节点心跳包"%cid)
                node = self.device.find_node(cid)
                if node:
                    node['alive'] = True
                    node['alive_time'] = datetime.now()
                    self.client.publish('user/%s/dev/%s'%(USER_ID,cid),payload=NODE_ONLINE)
                # 更新节点时间

                # time.sleep(0.5)
                # self.ser_send_to_node(cid=cid, cmd=NODE_RESPONSE_CONNECT)
                #  timer = threading.Timer(0.5, self.ser_send_to_node, (cid,NODE_RESPONSE_CONNECT))
                #  timer.start()

    def cmd_deal(self, recv):
        '''串口处理命令，监测校验和是否正确'''
        if recv.startswith('AA'):
            data = recv[4:-2]
            theory_len =  int(recv[2:4], 16)
            real_len = int(len(data)/2)
            if theory_len == real_len:
                data_list = [int(data[i:i+2], 16) for i in range(0,len(data)) if i%2==0]
                real_sum = sum(data_list) % 256
                theory_sum = int(recv[-2:], 16)
                if real_sum == theory_sum:
                    addr    = data[0:8]
                    cmd     = data[8:10]
                    content = data[10:]
                    return addr.lower(), cmd, content

    def ser_logging(self, level, info):
        print(level, info)

    def ser_send_to_node(self, cid='', cmd='', data=''):
        """串口发送消息给节点"""
        node = self.device.find_node(cid)
        if node:
            cmd = self.get_lora_cmd(lora_addr=node['lora_addr'],
                                    cid=node['cid'],
                                    cmd=cmd,
                                    data=data)
            if cmd:
                self.ser.on_send(cmd)

    def get_lora_cmd(self, lora_addr='', cid='', cmd='',data=''):
        """获取lora命令"""
        header = 'AA'
        data = cid+cmd+data
        length = hex(int(len(data)/2)).replace('0x','').zfill(2)[-2:]
        if len(data)>0 and len(data) % 2 == 0:
            data_list = [int(data[i:i+2], 16) for i in range(0,len(data)) if i%2==0]
            _sum = hex(sum(data_list) % 256).replace('0x','').zfill(2)[-2:]
            cmd = lora_addr+header+length+data+_sum
            print("发送命令：",cmd)
            cmd_list = [int(cmd[i:i+2],16) for i in range(0,len(cmd)) if i%2==0]
            cmd_bytes = bytes(cmd_list)
            return cmd_bytes

    def do_something(self):
        while True:
            time.sleep(2)
            if not self.get_nodes_flag:
                print("请求服务器，获取节点设备信息")
                print('server/%s/dev/%s/get/nodes'%(SERVER_ID, MQTT_CLIENT_ID))
                self.client.publish('server/%s/dev/%s/get/nodes'%(SERVER_ID, MQTT_CLIENT_ID))

    def heartHandler(self):
        """
        节点心跳包处理线程

        思路：
                客户端连接上服务端后，在服务端会维护一个在线客户端列表。
            客户端每隔一段时间，向服务端发送一个心跳包，服务端受收到包以后，会更新客户端最近一次在线时间。
            一旦服务端超过规定时间没有接收到客户端发来的包，则视为掉线。

            1、打开一个线程，扫描在线的节点设备。
            2、根据节点设备的心跳包更新的最近在线时间和当前时间做差值
            3、超过规定时间，修改设备在线状态

        """
        while True:
            time.sleep(5)
            for node in self.device.nodes:
                if node['alive']:
                    invertal_time = datetime.now() - node['alive_time']
                    if invertal_time.seconds > 12:
                        print("%s节点连接超时,下线"%node['cid'])
                        node['alive'] = False
                        self.client.publish('user/%s/dev/%s'%(USER_ID,node['cid']),payload=NODE_OFFLINE)


if __name__ == '__main__':
    ras = Raspberry()



